use std::{
    collections::HashSet,
    hash::{Hash, Hasher},
};

use anyhow::Result;

use chrono::{DateTime, Days, Utc};
use fake::{
    faker::{chrono::zh_cn::DateTimeBetween, internet::en::SafeEmail, name::zh_cn::Name},
    Dummy, Fake, Faker,
};

use rand::Rng;
use serde::{Deserialize, Serialize};
use sqlx::{Executor, PgPool};
use tokio::time::Instant;
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Deserialize, Dummy)]
enum Gender {
    Male,
    Female,
    Unknown,
}
#[derive(Clone, Debug, Serialize, Dummy, PartialEq, Eq, Deserialize, sqlx::FromRow)]
struct UserStat {
    #[dummy(faker = "UniqueEmail")]
    pub email: String,
    #[dummy(faker = "Name()")]
    pub name: String,
    pub gender: Gender,
    #[dummy(faker = "DateTimeBetween(before(5*365), before(89))")]
    pub created_at: DateTime<Utc>,
    #[dummy(faker = "DateTimeBetween(before(30), now())")]
    pub last_visited_at: DateTime<Utc>,
    #[dummy(faker = "DateTimeBetween(before(90), now())")]
    pub last_watched_at: DateTime<Utc>,
    #[dummy(faker = "IntList(50, 100000, 100000)")]
    pub rencet_watched: Vec<i32>,
    #[dummy(faker = "IntList(50, 200000, 100000)")]
    pub viewd_but_not_started: Vec<i32>,
    #[dummy(faker = "IntList(50, 300000, 100000)")]
    pub started_but_not_finished: Vec<i32>,
    #[dummy(faker = "IntList(50, 400000, 100000)")]
    pub finished: Vec<i32>,
    #[dummy(faker = "DateTimeBetween(before(45), now())")]
    pub last_email_notification: DateTime<Utc>,
    #[dummy(faker = "DateTimeBetween(before(15), now())")]
    pub last_in_app_notification: DateTime<Utc>,
    #[dummy(faker = "DateTimeBetween(before(90), now())")]
    pub last_sms_notification: DateTime<Utc>,
}

impl Hash for UserStat {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.email.hash(state)
    }
}
#[tokio::main]
async fn main() -> Result<()> {
    let pool = PgPool::connect("postgres://statsdata:123456@family01:5432/stats").await?;
    for i in 1..=100 {
        let users: HashSet<_> = (0..10000).map(|_| Faker.fake::<UserStat>()).collect();
        let start = Instant::now();
        bulk_insert(users, &pool).await?;
        println!("Batch {i} inserted in {:?}", start.elapsed());
    }
    Ok(())
}

async fn bulk_insert(users: HashSet<UserStat>, pool: &PgPool) -> Result<()> {
    let mut tx = pool.begin().await?;
    for user in users {
        let query=sqlx::query(
            r#"
        INSERT INTO user_stat(email,name,created_at,last_visited_at,last_watched_at,rencet_watched,viewd_but_not_started,started_but_not_finished,finished, last_email_notification, last_in_app_notification, last_sms_notification)
        VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
        "#,
        )
            .bind(&user.email)
            .bind(&user.name)
            .bind(user.created_at)
            .bind(user.last_visited_at)
            .bind(user.last_watched_at)
            .bind(user.rencet_watched)
            .bind(user.viewd_but_not_started)
            .bind(user.started_but_not_finished)
            .bind(&user.finished)
            .bind(user.last_email_notification)
            .bind(user.last_in_app_notification)
            .bind(user.last_sms_notification);
        tx.execute(query).await?;
    }
    tx.commit().await?;
    Ok(())
}
fn before(days: u64) -> DateTime<Utc> {
    Utc::now().checked_sub_days(Days::new(days)).unwrap()
}

fn now() -> DateTime<Utc> {
    Utc::now()
}

struct IntList(pub i32, pub i32, pub i32);

impl Dummy<IntList> for Vec<i32> {
    fn dummy_with_rng<R: Rng + ?Sized>(v: &IntList, rng: &mut R) -> Vec<i32> {
        let (max, start, len) = (v.0, v.1, v.2);
        let size = rng.gen_range(0..max);
        (0..size)
            .map(|_| rng.gen_range(start..start + len))
            .collect()
    }
}

const ALPHABET: [char; 36] = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
    'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
];

struct UniqueEmail;
impl Dummy<UniqueEmail> for String {
    fn dummy_with_rng<R: Rng + ?Sized>(_: &UniqueEmail, rng: &mut R) -> String {
        let email: String = SafeEmail().fake_with_rng(rng);
        let id = nanoid::nanoid!(8, &ALPHABET);
        let at = email.find("@").unwrap();
        let em = format!("{}.{}{}", &email[..at], id, &email[at..]);
        em
    }
}
